<?php
// +----------------------------------------------------------------------
// | ZGUAN [ 致力于帮助更多的中小企业物联网转型 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2017~2024 https://www.zguan.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ZGUAN 并不是自由软件，未经许可不能去掉ZGUAN 相关版权
// +----------------------------------------------------------------------
// | Author: ZGUAN Team <admin@zguan.cn>  0769-82282996  15899929162
// +----------------------------------------------------------------------
namespace app\common\repositories\bill;

use app\helper\Time;
use think\facade\Db;
use app\common\dao\bill\BillBasicDao;
use app\common\repositories\BaseRepository;
use app\common\repositories\property\basic\PropertyBasicRepository;

/**
 * Class BillBasicRepository
 *
 * @mixin BillBasicDao
 */
class BillBasicRepository extends BaseRepository
{
    const BILL_TYPE = [
        1 => '租赁账单',
        2 => '退租账单',
        4 => '记账单',
        8 => '预付费充值',
        9 => '押金单',
    ];

    public function __construct(BillBasicDao $dao)
    {
        $this->dao = $dao;
    }


    public function getPropertyList(array $where, $page, $limit, int $propertyId = null)
    {
        $query = $this->dao->search($where, $propertyId);
        $count = $query->count();
        $list = $query->page($page, $limit)
            ->with([
                'roomInfo' => function ($query) {
                    $query->bind(['full_room_name']);
                },
            ])
            ->append(['ar_amount', 'minus_amount', 'overdue_fine', 'total_amount', 'check_amount', 'stay_check_amount', 'stay_payment_amount'])
            ->order('id', 'desc')
            ->select();
        return compact('count', 'list');
    }

    public function getApiList(array $where, $page, $limit, int $propertyId = null)
    {
        $query = $this->dao->search($where, $propertyId);
        $count = $query->count();
        $list = $query->page($page, $limit)
            ->with([
                'userInfo' => function ($query) {
                    $query->field('id,username,mobile');
                    $query->bind(['username', 'mobile']);
                },
                'roomInfo' => function ($query) {
                    $query->bind(['full_room_name']);
                }
            ])
            ->field('id,month_time,number,cutoff_date,status,room_id,user_id')
            ->append(['ar_amount', 'total_amount', 'stay_check_amount', 'stay_payment_amount'])
            ->order('id', 'desc')
            ->select();
        return compact('count', 'list');
    }


    public function addInfo(array $data, int $userId)
    {
        return Db::transaction(function () use ($data, $userId) {
            $otherFee = [];
            if (isset($data['other_fee']) && is_array($data['other_fee'])) {
                $otherFee = $data['other_fee'];
            }
            unset($data['other_fee']);
            $data['create_date'] = date('Y-m-d');
            $billInfo = $this->dao->create($data);
            if (isset($otherFee) && is_array($otherFee)) {
                /** @var BillCatalogRepository $billCatalogRepository */
                $billCatalogRepository = app()->make(BillCatalogRepository::class);
                $billCatalogRepository->addAll($billInfo, $otherFee);
            }

            ## 账单创建事件
            event('bill.create', [
                'billInfo' => $billInfo,
                'userId' => $userId
            ]);
            return $billInfo;
        });
    }

    public function editInfo($info, array $data, int $userId = 0)
    {
        return Db::transaction(function () use ($data, $info, $userId) {
            ## 账单创建事件
            event('bill.upgrade', [
                'billInfo' => $info,
                'userId' => $userId
            ]);
            return $this->dao->update($info['id'], $data);
        });
    }


    public function getDetail(int $id)
    {
        $data = $this->dao->search([])
            ->with([
                'roomInfo' => function ($query) {
                    $query->bind(['full_room_name']);
                },
            ])
            ->where('id', $id)
            ->find();
        return $data;
    }

    public function getApiDetail(int $id)
    {
        $data = $this->dao->getSearch([])
            ->where('id', $id)
            ->with([
                'userInfo' => function ($query) {
                    $query->field('id,username,mobile');
                    $query->bind(['username', 'mobile']);
                },
                'roomInfo' => function ($query) {
                    $query->bind(['full_room_name']);
                }
            ])
            ->field('id,month_time,number,cutoff_date,status,room_id,user_id')
            ->append(['ar_amount', 'total_amount', 'stay_check_amount', 'stay_payment_amount'])
            ->find();
        return $data;
    }

    public function batchDelete(array $ids, int $userId = 0, string $remark = '')
    {
        $list = $this->dao->selectWhere([
            ['id', 'in', $ids], ['status', 'in', [4, 9]]
        ], 'id,property_id,status,number');
        if ($list) {
            $num = 0;
            foreach ($list as $k => $v) {
                $this->deleteInfo($v, $userId, $remark);
                $num++;
            }
            return $num;
        }
        return [];
    }

    public function deleteInfo($billInfo, int $userId = 0, string $remark = '')
    {
        return Db::transaction(function () use ($billInfo, $userId, $remark) {
            ## 账单删除事件
            event('bill.delete', [
                'remark' => $remark,
                'billInfo' => $billInfo,
                'userId' => $userId
            ]);
        });
    }

    public function generateBillNumber(int $propertyId)
    {
        /** @var PropertyBasicRepository $propertyBasicRepository */
        $propertyBasicRepository = app()->make(PropertyBasicRepository::class);
        $codeInfo = $propertyBasicRepository->getSearch([])
            ->when($propertyId, function ($query) use ($propertyId) {
                $query->where('id', $propertyId);
            })
            ->value('code');
        return 'B' . date('ymd') . rand(1111, 9999) . ($codeInfo ?? '00000');
    }

    /**
     * 批量审核账单
     */
    public function batchAuditBill(array $ids, int $userId)
    {
        $list = $this->dao->selectWhere([
            ['id', 'in', $ids], ['status', 'in', [1]]
        ], 'id,property_id,status,number');
        if ($list) {
            $num = 0;
            foreach ($list as $k => $v) {
                $this->auditBillInfo($v, $userId);
                $num++;
            }
            return $num;
        }
        return [];
    }

    ## 账单审核
    public function auditBillInfo($billInfo, int $userId)
    {
        return Db::transaction(function () use ($billInfo, $userId) {
            $this->dao->update($billInfo['id'], [
                'status' => 2,
                'check_user_id' => $userId,
                'check_time' => date('Y_m-d H:i:s'),
            ]);
            ## 账单审核事件
            event('bill.approval', [
                'billInfo' => $billInfo,
                'userId' => $userId,
            ]);
        });
    }


    /**
     * 作废账单
     */
    public function batchInvalidBill(array $ids, int $userId, string $invalidRemark = '')
    {
        $list = $this->dao->selectWhere([
            ['id', 'in', $ids], ['status', 'in', [1, 2, 5, 8]]
        ], 'id,property_id,status,number');
        if ($list) {
            $num = 0;
            foreach ($list as $k => $v) {
                $this->invalidBillInfo($v, $userId, $invalidRemark);
                $num++;
            }
            return $num;
        }
        return [];
    }

    public function invalidBillInfo($billInfo, int $userId, string $invalidRemark = '')
    {
        return Db::transaction(function () use ($billInfo, $userId, $invalidRemark) {
            $editData['status'] = 4;
            if ($invalidRemark) {
                $editData['check_remark'] = $invalidRemark;
            }
            $editData['check_user_id'] = $userId ?? '0';
            $editData['check_time'] = date('Y_m-d H:i:s');
            $this->dao->update($billInfo['id'], $editData);

            # 账单作废事件
            event('bill.invalid', [
                'billInfo' => $billInfo,
                'userId' => $userId,
            ]);
        });
    }


    /**
     * 催缴提醒
     * @param array $ids
     * @param int $userId
     * @param string $invalidRemark
     * @return array|int
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     *
     * @author Amos
     * 2024-06-17 19:18
     */
    public function batchUrgeBill(array $ids, int $userId)
    {
        $list = $this->dao->selectWhere([
            ['id', 'in', $ids], ['status', 'in', [2, 5, 8]]
        ], 'id,property_id,status,urge_num,number,month_time');
        if ($list) {
            $num = 0;
            foreach ($list as $k => $v) {
                $this->urgeBillInfo($v, $userId);
                $num++;
            }
            return $num;
        }
        return [];
    }

    /**
     * 催缴提醒
     * @param $billInfo
     * @param int $userId
     * @param string $invalidRemark
     * @return mixed
     *
     * @author Amos
     * 2024-06-17 19:19
     */
    public function urgeBillInfo($billInfo, int $userId)
    {
        return Db::transaction(function () use ($billInfo, $userId) {
            $editData['urge_num'] = $billInfo['urge_num'] + 1;
            $this->dao->update($billInfo['id'], $editData);

            # 账单催缴事件
            event('bill.payUrge', [
                'billInfo' => $billInfo,
                'userId' => $userId,
            ]);
        });
    }


    /**
     * 延期账单
     */
    public function batchPostponeBill(array $ids, int $day, int $userId)
    {
        $list = $this->dao->selectWhere([
            ['id', 'in', $ids]
        ], 'id,property_id,status,number,cutoff_date,overdue_day');
        if ($list) {
            $num = 0;
            foreach ($list as $k => $v) {
                $this->postponeBillInfo($v, $day, $userId);
                $num++;
            }
            return $num;
        }
        return [];
    }

    public function postponeBillInfo($billInfo, $day, int $userId)
    {
        return Db::transaction(function () use ($billInfo, $day, $userId) {
            $time = $day * 86400;
            $editData['cutoff_date'] = date('Y-m-d', strtotime($billInfo['cutoff_date']) + $time);
            if ($editData['cutoff_date'] >= date('Y-m-d') && $billInfo['status'] != 2) {
                $editData['status'] = 2;
            }
            $this->dao->update($billInfo['id'], $editData);
            #延期账单事件
            event('bill.postpone', [
                'day' => $day,
                'userId' => $userId,
                'billInfo' => $billInfo,
                'cutoff_date' => $editData['cutoff_date'],
            ]);
        });
    }

    ## 账单支付
    public function paymentBill($billInfo, $param, int $userId)
    {
        return Db::transaction(function () use ($billInfo, $param, $userId) {
            $picture = [];
            if (isset($param['picture'])) {
                $picture = $param['picture'];
                unset($param['picture']);
            }
            /** @var BillCourseRepository $billCourseRepository */
            $billCourseRepository = app()->make(BillCourseRepository::class);
            $courseData['pay_type'] = 2;
            $courseData['check_type'] = 1;
            $courseData['check_method'] = 2;
            $courseData['bill_id'] = $billInfo['id'];
            $courseData['room_id'] = $billInfo['room_id'];
            $courseData['pay_remark'] = $param['remark'];
            $courseData['pay_amount'] = $billInfo['stay_payment_amount'];
            $courseData['pay_method'] = $param['pay_method'];
            $courseData['pay_time'] = date('Y_m-d H:i:s');
            $courseData['pay_user_id'] = $userId;
            $courseData['affirm_type'] = 1;
            $courseData['property_id'] = $billInfo['property_id'];
            $res = $billCourseRepository->create($courseData);

            if (isset($picture) && is_array($picture)) {
                /** @var BillPictureRepository $billPictureRepository */
                $billPictureRepository = app()->make(BillPictureRepository::class);
                $billPictureRepository->addPicture($billInfo['id'], $res['id'], $picture);
            }
            $billInfo->status = 9;
            $billInfo->save();
            ## 账单支付事件
            event('bill.paySuccess', [
                'payType' => 2,
                'status' => 9,
                'userId' => $userId,
                'billInfo' => $billInfo,
                'payAmount' => $billInfo['stay_payment_amount'],
                'payMethod' => $param['pay_method'],
            ]);
            return $res;
        });
    }


    /**
     * 退租结算 作废账单
     * @param int $contractId
     * @param $note
     * @return void
     * @throws \think\db\exception\DbException
     *
     * @author Amos
     * 2024-03-20 15:14
     */
    public function invalidBillByContractId($billInfo, int $userId, $invalidRemark = '')
    {
        $editData['status'] = 4;
        if ($invalidRemark) {
            $editData['check_remark'] = $invalidRemark;
        }
        $editData['check_user_id'] = $userId ?? '0';
        $editData['check_time'] = date('Y_m-d H:i:s');
        $this->dao->update($billInfo['id'], $editData);

        # 账单作废事件
        event('bill.invalid', [
            'billInfo' => $billInfo,
            'userId' => $userId,
            'remark' => $invalidRemark,
        ]);
    }

    /**
     *  进入待支付页面 检测账单支付状态
     * @return void
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     *
     * @author Amos
     * 2024-05-15 23:44
     */
    public function checkStayPaymentListBill()
    {
        $list = $this->dao->selectWhere([
            ['status', 'in', [2, 8]]
        ], 'id');
        if ($list) {
            foreach ($list as $k => $v) {
                $this->monitorBillPayStatus($v['id']);
            }
        }
    }


    /**
     * 监测 账单支付状态
     * @param int $billId
     * @return void
     *
     * @author Amos
     * 2024-04-26 22:55
     */
    public function monitorBillPayStatus(int $billId)
    {
        $billInfo = $this->dao->get($billId);
        if ($billInfo['stay_payment_amount'] <= 0) {
            $billInfo->status = 9;
            $billInfo->save();
        } else if ($billInfo['stay_check_amount'] > 0 || $billInfo['stay_payment_amount'] > 0) {
            $billInfo->status = 8;
            $billInfo->save();
        }
    }

    /**
     * 根据物业 ID  数据统计
     * @param int $propertyId
     * @return array
     *
     * @author Amos
     * 2024-06-14 09:25
     */
    public function getStatistics(int $propertyId, string $month = null)
    {
        $data = $this->dao->getSearch([])
            ->when($propertyId, function ($query) use ($propertyId) {
                $query->where('property_id', $propertyId);
            })
            ->when($month, function ($query) use ($month) {
                $query->where('month_time', $month);
            })
            ->field('id,property_id,status')
            ->group('status')
            ->column('count(*)', 'status');
        ## 1待审核 2 待支付 3待确认 4已作废 5已逾期 8 支付中 9 己完成

        /** @var BillCatalogRepository $billCatalogRepository */
        $billCatalogRepository = app()->make(BillCatalogRepository::class);

        ## 账单总额
        $arrBillAmount = $billCatalogRepository->getPropertyIdArrAmount($propertyId, ['month_time' => $month]);
        ## 己收款总额
        $actualAmount = $this->getPropertyIdActualAccount($propertyId, $month);

        return [
            'stay_audit_num' => $data[1] ?? 0,
            'stay_pay_num' => $data[2] ?? 0,
            'stay_check_num' => $data[3] ?? 0,
            'overdue_num' => $data[5] ?? 0,
            'pay_num' => $data[8] ?? 0,
            'in_void_num' => $data[4] ?? 0,
            'succeed_num' => $data[9] ?? 0,
            'overdue_one_num' => $this->getExpiringSoonNum($propertyId, 1),
            'overdue_two_num' => $this->getExpiringSoonNum($propertyId, 2),
            'overdue_three_num' => $this->getExpiringSoonNum($propertyId, 3),
            'arr_bill_amount' => $arrBillAmount,
            'energy_amount' => $billCatalogRepository->getPropertyIdArrAmount($propertyId, ['month_time' => $month, 'is_class' => 2, 'set_finance' => 0]),
            'other_amount' => $billCatalogRepository->getPropertyIdArrAmount($propertyId, ['month_time' => $month, 'is_class' => 3, 'set_finance' => 0]),
            'rent_amount' => $billCatalogRepository->getPropertyIdArrAmount($propertyId, ['month_time' => $month, 'is_class' => 1, 'set_finance' => 4]),
            'deposit_amount' => $billCatalogRepository->getPropertyIdArrAmount($propertyId, ['month_time' => $month, 'is_class' => 1, 'set_finance' => 3]),
            'actual_account' => $actualAmount,
            'stay_pay_amount' => bcsub($arrBillAmount, $actualAmount, 2),
        ];
    }

    /**
     *  根据物业 ID 获取将逾期账单
     * @param int $propertyId
     * @return int
     * @throws \think\db\exception\DbException
     *
     * @author Amos
     * 2024-06-13 18:27
     */
    public function getExpiringSoonNum(int $propertyId = 0, $day = 1)
    {
        $today = date('Y-m-d');
        $daysAfter = date('Y-m-d', Time::daysAfter($day));
        return $this->dao->getSearch([])
            ->when($propertyId, function ($query) use ($propertyId) {
                $query->where('property_id', $propertyId);
            })
            ->whereIn('status', '2,8')
            ->where('cutoff_date', 'between', [$today, $daysAfter])
            ->count();
    }


    /**
     *  根据物业 ID 统计收款金额
     * @param int $propertyId
     * @return int
     * @throws \think\db\exception\DbException
     *
     * @author Amos
     * 2024-06-13 18:27
     */
    public function getPropertyIdActualAccount(int $propertyId = 0, string $month = null, $type = null)
    {
        ## 已收款金额【线上】
        $onlineAmount = $this->dao->getSearch([])
            ->when($propertyId, function ($query) use ($propertyId) {
                $query->where('property_id', $propertyId);
            })
            ->when($month, function ($query) use ($month) {
                $query->where('month_time', $month);
            })
            ->sum('online_amount');

        ## 已收款金额【线下】
        $offlineAmount = $this->dao->getSearch([])
            ->when($propertyId, function ($query) use ($propertyId) {
                $query->where('property_id', $propertyId);
            })
            ->when($month, function ($query) use ($month) {
                $query->where('month_time', $month);
            })
            ->sum('offline_amount');

        if (isset($type) && $type == 1) {
            return $onlineAmount;
        } else if (isset($type) && $type == 2) {
            return $offlineAmount;
        } else {
            return bcadd($onlineAmount, $offlineAmount, 2);
        }
    }


}